tomcat7のWebSocketでcanvasの描画を共有する
こんにちは。tomcat7でWebSocketを使ってみました。
今回はcanvasに書いた情報を共有するといことをやりたいと思います。
使用する環境はpleiades3.7.2、Java7になります。またEclipseのtomcatホームの設定はしてあるものとして進めます。
Eclipseでプロジェクトを作成します
「ファイル」>「新規」>「その他」>「Java」からTomcatプロジェクトを選択し、プロジェクトを作成します。
(今回はサブディレクトリにWebContentsを設定して作成しました)
srcフォルダ配下にws.appパッケージを作成します。ここにサーバ側のソースを格納します。
クライアント側のソースはWebContents直下にhtmlファイルを作成します。
以下フォルダ構成になります。
ビルドパスを追加します
tomcatでwebsocketを実装するのにはWebSocketServletを継承してサーブレットを実装する必要があります。
それにはcatalina.jarとtomcat-coyote.jarが必要なのでビルドバスに追加をします。
プロジェクトを右クリックして「プロパティ」から「Javaのビルド・バス」を選択します。ライブラリタブから「変数の追加」ボタンをクリックし、「TOMCAT_HOME」を選択します。次に拡張ボタンをクリックし、「lib」フォルダからcatalina.jarを選択します。同様にtomcat-coyote.jarも追加します。
サーブレットを作成します
ws.app配下にDrawServletクラスを作成します。
package ws.app; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.List; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import org.apache.catalina.websocket.MessageInbound; import org.apache.catalina.websocket.StreamInbound; import org.apache.catalina.websocket.WebSocketServlet; import org.apache.catalina.websocket.WsOutbound; @WebServlet(urlPatterns={"/DrawServlet"}) public class DrawServlet extends WebSocketServlet{ private static final long serialVersionUID = 1L; private static List<DrawMessageInbound> messageList = new ArrayList<DrawMessageInbound>(); private class DrawMessageInbound extends MessageInbound{ WsOutbound drawOutbound; // 接続時の処理 @Override public void onOpen(WsOutbound outbound){ System.out.println("open"); this.drawOutbound = outbound; messageList.add(this); } // 接続解除時の処理 @Override public void onClose(int status){ System.out.println("close"); messageList.remove(this); } // メッセージ受信時の処理 @Override public void onTextMessage(CharBuffer message) throws IOException{ System.out.println("message"+ message); for(DrawMessageInbound in: messageList){ CharBuffer buffer = CharBuffer.wrap(message); in.drawOutbound.writeTextMessage(buffer); in.drawOutbound.flush(); } } // メッセージ受信時の処理 @Override public void onBinaryMessage(ByteBuffer bb) throws IOException{ } } @Override public StreamInbound createWebSocketInbound(String arg0, HttpServletRequest arg1) { return new DrawMessageInbound(); } }
ソースについて簡単に説明します。
冒頭にも書きましたが、WebSocketServletを継承する必要があります。アノテーションはURLマッピングのためのものになります。Servlet3.0ではHttpServletクラスを継承していれば、@WebServletアノテーションを書くだけでweb.xmlを記載しなくてもサーブレットを呼び出すことができます。WebSocketServletクラスはHttpServletを継承してているクラスなので記述しておけばweb.xmlがなくても呼び出されます。
@WebServlet(urlPatterns={"/DrawServlet"}) public class DrawServlet extends WebSocketServlet{ }
WebSocketServletクラスを継承するとcreateWebSocketInboundを実装する必要があります。クライアントからの要求があるとこのメソッドが呼び出されます。またWebSoket通信はStreamInboundクラスを返却する必要があります。ここではMessageInboundクラス(StreamInboundを継承したクラス)を継承したDrawMessageInboundクラスを生成して返却しています。
@Override public StreamInbound createWebSocketInbound(String arg0, HttpServletRequest arg1) { return new DrawMessageInbound(); }
接続時の処理を記述します。ここでは自身の内部クラスをリストに追加しています。
// 接続時の処理 @Override public void onOpen(WsOutbound outbound){ System.out.println("open"); this.drawOutbound = outbound; messageList.add(this); }
接続解除時の処理を記述します。接続時に追加した自身をリストから削除します。
@Override public void onClose(int status){ System.out.println("close"); messageList.remove(this); }
メッセージ受信時の処理を記述します。ここではクライアントから送信されたメッセージ(マウス座標の文字列)を接続された全てに対してメッセージを送信しています。
// メッセージ受信時の処理 @Override public void onTextMessage(CharBuffer message) throws IOException{ System.out.println("message"+ message); for(DrawMessageInbound in: messageList){ CharBuffer buffer = CharBuffer.wrap(message); in.drawOutbound.writeTextMessage(buffer); in.drawOutbound.flush(); } }
バイナリメッセージを受信した時の処理を記述します。今回は文字列のみを取り扱うので何も実装しません。
// メッセージ受信時の処理 @Override public void onBinaryMessage(ByteBuffer bb) throws IOException{ }
HTMLファイルの作成
WebContentsフォルダ直下にindex.htmlを作成します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>canvas</title> <script> var ws = new WebSocket("ws://localhost:8080/websocket/DrawServlet"); ws.onopen = function(){ // 接続時の処理 }; var isMouseDown = false; var canvas; var context; window.onload = function() { canvas = document.getElementById("canvas1"); context = canvas.getContext('2d'); canvas.addEventListener("mousemove", function(event){ mouseX = event.pageX; mouseY = event.pageY; if(isMouseDown){ draw(mouseX, mouseY); ws.send(mouseX + "," + mouseY); } }); canvas.addEventListener("mousedown", function(){ isMouseDown = true; }); canvas.addEventListener("mouseup", function(){ isMouseDown = false; }); } // 描画処理 function draw(x, y){ context.beginPath(); context.fillStyle = "#0099ff"; context.arc(x, y, 10, 0, Math.PI*2, false); context.fill(); } ws.onmessage = function(message){ // 受信時の処理 var value = message.data.split(","); draw(value[0], value[1]); }; // 接続解除 function closeConnect(){ ws.close(); } </script> </head> <body> <div> <canvas id="canvas1" width="500" height="400" style="background-color:#999999;"> </canvas> </div> <input type="button" value="接続解除" onclick="closeConnect()"> </body> </html>
クライアント側のソースについて説明をします。
サーバに対して接続を行っています。
var ws = new WebSocket("ws://localhost:8080/websocket/DrawServlet");
ページを開いた時の初期処理を記述しています。ここではcanvasを取得してmousemove、mousedown、mouseupのイベントを登録しています。
window.onload = function() { canvas = document.getElementById("canvas1"); context = canvas.getContext('2d'); canvas.addEventListener("mousemove", function(event){ mouseX = event.pageX; mouseY = event.pageY; if(isMouseDown){ draw(mouseX, mouseY); ws.send(mouseX + "," + mouseY); } }); canvas.addEventListener("mousedown", function(){ isMouseDown = true; }); canvas.addEventListener("mouseup", function(){ isMouseDown = false; }); }
mousemoveイベント内ではマウスがクリックされた状態で、マウスが動いた場合にdrawメソッドを呼び出し、線を引きサーバに対してカンマ区切りでマウスのX、Y座標を送信しています。
if(isMouseDown){ draw(mouseX, mouseY); ws.send(mouseX + "," + mouseY); }
サーバからの受信時に呼び出されます。受信メッセージをカンマで分割しX、Y座標を取得し描画メソッドを呼び出しています。
ws.onmessage = function(message){ // 受信時の処理 var value = message.data.split(","); draw(value[0], value[1]); };
サーバとの接続を解除します。
function closeConnect(){ ws.close(); }
動かしてみましょう
http://localhost:8080/websocketにブラウザからアクセスしてみます。
ブラウザ複数起動させます。canvasに線を引くと別ブラウザにも線が引かれるのが分かります。ローカル環境だとスムーズに描画されるかと思います。 接続解除を押すとサーバとの接続が解除されるので描画が共有されなくなります。
最後に
tomcatを使って簡単にWebSocket通信が出来たかと思います。ただしWebSocketの仕様については仕様が確定していませんので、今後変わる可能性があるみたいです。
Java7EEではWebSocket APIが含まれる予定なので、そうすれば 仕様も確定するのかなと思います。